﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using NGinnBPM.Runtime.Messages;
using System.Collections;
using System.Xml.Xsl;
using NGinnBPM.Lib.Data;
using NGinnBPM.Lib.Schema;
using NGinnBPM.Lib.Interfaces;
using System.IO;
using System.Xml;
using NGinnBPM.Services;

namespace NGinnBPM.Runtime.Tasks
{
    /// <summary>
    /// This task makes http GET/POST/PUT/DELETE requests
    /// </summary>
    [Serializable]
    [DataContract]
    public class HttpRequestTaskInstance : AtomicTaskInstance
    {
        public HttpRequestTaskInstance()
        {
            HttpMethod = "GET";
        }

        /// <summary>
        /// Http method: GET/POST/PUT/DELETE
        /// </summary>
        [DataMember]
        [TaskParameter(IsInput = true, Required=true)]
        public string HttpMethod { get; set; }

        /// <summary>
        /// Request URL
        /// </summary>
        [DataMember]
        [TaskParameter(IsInput = true, Required = true)]
        public string Url { get; set; }

        /// <summary>
        /// Dictionary of request headers
        /// </summary>
        [DataMember]
        [TaskParameter(IsInput = true, Required = false)]
        public Hashtable RequestHeaders { get; set; }
        /// <summary>
        /// Dictionary of received response headers
        /// </summary>
        [DataMember]
        [TaskParameter(IsInput = false, Required = true)]
        public Hashtable ResponseHeaders { get; set; }

        [DataMember]
        [TaskParameter(IsInput = true, Required = false)]
        public Hashtable RequestParams { get; set; }

        [IgnoreDataMember]
        [TaskParameter(IsInput = true, Required = false)]
        public string RequestBodyText { get; set; }

        public enum ResponseContentType
        {
            Xml, ///parse response text as xml and update task data accordingly
            Json, ///parse response text as json and update task data accordingly
            Text, ///store response text in ResponseText parameter
            Binary, ///don't know yet what to do with that,
            SOAP    ///soap request/response (its xml with additional validation)
        }

        /// <summary>
        /// Interpret response as
        /// </summary>
        [DataMember]
        [TaskParameter(IsInput = true)]
        public ResponseContentType ResponseType { get; set; }

        /// <summary>
        /// Http status
        /// </summary>
        [DataMember]
        [TaskParameter(IsInput = false)]
        public int HttpStatus { get; set; }


        public enum RequestModes
        {
            Xml, //xml body post
            Json, //json body post
            Form, //form post
            Text,  //send RequestBodyText
            SOAP //web service call (xml)
        }

        /// <summary>
        /// Http request mode
        /// Xml - will send task data as Xml
        /// Json - will send task data as Json
        /// </summary>
        [DataMember]
        [TaskParameter(IsInput = true, Required=true)]
        public RequestModes RequestType { get; set; }

        [DataMember]
        [TaskParameter(IsInput = true, Required = false)]
        public string UserName { get; set; }
        
        [IgnoreDataMember]
        [TaskParameter(IsInput = true, Required = false)]
        public string Password { get; set; }

        /// <summary>
        /// Xslt used for transforming request xml
        /// </summary>
        [DataMember]
        [TaskParameter(IsInput = true, Required = false)]
        public string RequestBodyXsl { get; set; }

        /// <summary>
        /// Xslt file used for transforming xml response
        /// </summary>
        [DataMember]
        [TaskParameter(IsInput = true, Required = false)]
        public string ResponseBodyXsl { get; set; }

        [IgnoreDataMember]
        [TaskParameter(IsInput=false)]
        public string ResponseText { get; set; }

        [TaskParameter(IsInput = true, Required = false)]
        public string ExpectedFault { get; set; }

        public override void Enable(Dictionary<string, object> inputData)
        {
            base.Enable(inputData);
            SendHttpRequest shr = PrepareRequest();
            IHttpClient cli = GetHttpClient();
            SendHttpRequestResponse resp = cli.Execute(shr);
            HandleResponse(resp);
        }


        private IHttpClient GetHttpClient()
        {
            return Context.ServiceLocator.GetInstance<IHttpClient>();
        }

        private Dictionary<string, object> GetRequestData()
        {
            Dictionary<string, object> dob = new Dictionary<string, object>();
            if (this.RequestParams != null && this.RequestParams.Count > 0)
            {
                foreach (string k in RequestParams.Keys)
                    dob[k] = RequestParams[k];
            }
            else
            {
                Task tsk = ParentProcess.GetTask(TaskId);
                foreach (VariableDef vd in tsk.Variables)
                {
                    if (vd.VariableDir == VariableDef.Dir.In || vd.VariableDir == VariableDef.Dir.InOut || vd.VariableDir == VariableDef.Dir.Local)
                    {
                        if (TaskData.ContainsKey(vd.Name)) dob[vd.Name] = TaskData[vd.Name];
                    }
                }
            }
            return dob;
        }

        private SendHttpRequest PrepareRequest()
        {
            SendHttpRequest shr = new SendHttpRequest();
            shr.Method = this.HttpMethod;
            shr.Url = this.Url;
            shr.Headers = new Hashtable();
            shr.User = this.UserName;
            shr.Passwd = this.Password;
            shr.CorrelationId = this.InstanceId;
            if (ExpectedFault != null && ExpectedFault.Length > 0)
            {
                shr.ExpectedFaultString = ExpectedFault;
                shr.SOAPFaultIsValid = true;
            }
            Dictionary<string, object> rqData = GetRequestData();
            if (this.HttpMethod == "GET" || this.HttpMethod == "HEAD" || this.HttpMethod == "DELETE")
            {
                shr.RequestData = new Hashtable();
                foreach (string f in rqData.Keys)
                    shr.RequestData[f] = rqData[f];
            }
            else if (this.HttpMethod == "POST" || this.HttpMethod == "PUT")
            {
                if (this.RequestType == RequestModes.Json)
                {
                    Dictionary<string, object> dob = GetRequestData();
                    shr.RequestBodyText = DataUtil.ToJson(dob);
                    shr.Headers["Content-Type"] = "application/json";
                }
                else if (this.RequestType == RequestModes.Xml || this.RequestType == RequestModes.SOAP)
                {
                    Dictionary<string, object> dob = GetRequestData();
                    shr.RequestBodyText = DataUtil.ToXml(dob);
                    if (RequestBodyXsl != null && RequestBodyXsl.Length > 0)
                    {
                        log.Debug("Transforming request body {0}", shr.RequestBodyText);
                        MemoryStream ms = new MemoryStream();
                        Transform(shr.RequestBodyText, RequestBodyXsl, ms);
                        log.Debug("Transform result: {0}", Encoding.UTF8.GetString(ms.GetBuffer()));
                        shr.GoBinary = true;
                        shr.RequestBodyText = Convert.ToBase64String(ms.GetBuffer());
                    }
                    if (RequestType == RequestModes.SOAP)
                        shr.Headers["Content-Type"] = "application/soap+xml";
                    else
                        shr.Headers["Content-Type"] = "application/xml";
                    
                }
                else if (this.RequestType == RequestModes.Form)
                {
                    shr.RequestData = new Hashtable();
                    Dictionary<string, object> dob = GetRequestData();
                    foreach (string f in dob.Keys)
                        shr.RequestData[f] = dob[f];
                }
                else if (this.RequestType == RequestModes.Text)
                {
                    shr.RequestBodyText = this.RequestBodyText;
                }
                else throw new Exception();
            }
            else throw new Exception("Invalid http method");
            if (this.RequestHeaders != null)
            {
                foreach (string h in this.RequestHeaders.Keys)
                    shr.Headers[h] = this.RequestHeaders[h];
            }
            return shr;
        }

        

        private void HandleResponse(SendHttpRequestResponse resp)
        {
            this.HttpStatus = resp.HttpStatus;
            this.ResponseHeaders = new Hashtable();
            foreach (string k in resp.ResponseHeaders.Keys)
                this.ResponseHeaders[k] = resp.ResponseHeaders[k];

            if (IsHttpSuccess(this.HttpStatus) || (HttpStatus == 500 && ResponseType == ResponseContentType.SOAP))
            {
                string respText = resp.ResponseText;
                if (resp.GoBinary)
                {
                    byte[] data = Convert.FromBase64String(resp.ResponseText);
                    respText = Encoding.UTF8.GetString(data);
                }
                if (this.ResponseType == ResponseContentType.Json)
                {
                    Dictionary<string, object> dob = DataUtil.ReadJson(new StringReader(respText));
                    Complete(null, dob);
                    return;
                }
                else if (this.ResponseType == ResponseContentType.Xml || ResponseType == ResponseContentType.SOAP)
                {
                    if (ResponseBodyXsl != null && ResponseBodyXsl.Length > 0)
                    {
                        respText = Transform(respText, ResponseBodyXsl);
                        log.Debug("Transformed response xml. Result: {0}", respText);
                    }
                    Dictionary<string, object> dob = DataUtil.ReadXml(new StringReader(respText));
                    Complete(null, dob);
                    return;
                }
                else if (this.ResponseType == ResponseContentType.Text)
                {
                    this.ResponseText = respText;
                    Complete(null, null);
                    return;
                }
                else if (this.ResponseType == ResponseContentType.Binary)
                {
                    throw new NotImplementedException();
                }
                else throw new NotImplementedException();
            }
            else
            {
                DefaultHandleTaskFailure(string.Format("Http request error: {0}", resp.HttpStatus), false);
            }
        }

        private bool IsHttpSuccess(int code)
        {
            return code >= 200 && code < 300;
        }

        private static Dictionary<string, XslCompiledTransform> _transformCache = new Dictionary<string, XslCompiledTransform>();
        
        private XslCompiledTransform GetTransform(string stylesheet)
        {
            string key = string.Format("{0}/{1}", ParentProcess.Package.Name, stylesheet);
            lock (_transformCache)
            {
                XslCompiledTransform t;
                if (_transformCache.TryGetValue(key, out t)) return t;
                log.Info("Loading XSL transform: {0}", key);
                XslCompiledTransform tr = new XslCompiledTransform();

                MemoryStream ms = new MemoryStream(Context.PackageRepository.GetPackageResource(ParentProcess.PackageName, stylesheet));
                tr.Load(XmlReader.Create(ms));
                _transformCache[key] = tr;
                return tr;
            }
        }

        

        private string Transform(string input, string stylesheet)
        {
            XslCompiledTransform tr = GetTransform(stylesheet);
            StringWriter sw = new StringWriter();
            StringReader sr = new StringReader(input);
            tr.Transform(XmlReader.Create(sr), XmlWriter.Create(sw));
            return sw.ToString();
        }

        private void Transform(string input, string stylesheet, Stream outStm)
        {
            XmlWriterSettings xws = new XmlWriterSettings();
            xws.Encoding = Encoding.UTF8;
            xws.Indent = true;
            XmlWriter xw = XmlWriter.Create(outStm, xws);
            XslCompiledTransform tr = GetTransform(stylesheet);
            StringReader sr = new StringReader(input);
            tr.Transform(XmlReader.Create(sr), xw);
            xw.Flush();
        }
    }
}
